package com.kewu.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.kewu.common.enums.ErrorCodeEnum;
import com.kewu.common.global.CustomException;
import com.kewu.common.global.request.PageParam;
import com.kewu.common.util.CronUtil;
import com.kewu.system.api.cons.QuartzConstants;
import com.kewu.system.api.entity.SysQuartz;
import com.kewu.system.api.enums.QuartzStatusEnum;
import com.kewu.system.dao.SysQuartzDao;
import com.kewu.system.job.QuartzJob;
import com.kewu.system.service.SysQuartzService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 定时任务(SysQuartz)表服务实现类
 *
 * @author wangning
 * @since 2024-05-29 14:07:38
 */
@Service("sysQuartzService")
@RequiredArgsConstructor
@Slf4j
public class SysQuartzServiceImpl implements SysQuartzService {
    private final SysQuartzDao sysQuartzDao;
    private final Scheduler scheduler;

    /**
     * 项目启动时，初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理（注：不能手动修改数据库ID和任务组名，否则会导致脏数据）
     */
    @PostConstruct
    public void init() throws SchedulerException {
        scheduler.clear();
        LambdaQueryWrapper<SysQuartz> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SysQuartz::getStatus, QuartzStatusEnum.WAIT_START.getCode());
        List<SysQuartz> sysQuartzList = sysQuartzDao.selectList(wrapper);
        for (SysQuartz sysQuartz : sysQuartzList) {
            scheduleAdd(sysQuartz);
            // 如果任务过期，则修改任务状态
            if (Objects.isNull(getNextExecution(sysQuartz.getCronExpression()))) {
                sysQuartz.setStatus(QuartzStatusEnum.EXPIRED.getCode());
                sysQuartzDao.update(sysQuartz);
                log.info("任务【{}】已过期，cron【{}】", sysQuartz.getId(), sysQuartz.getCronExpression());
            }
        }
    }

    /**
     * 新增定时任务
     *
     * @param sysQuartz 实例对象
     * @return 实例对象
     */
    @Override
    @Transactional
    public SysQuartz add(SysQuartz sysQuartz) throws SchedulerException, ParseException {
        LambdaQueryWrapper<SysQuartz> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SysQuartz::getClassName, sysQuartz.getClassName());
        SysQuartz one = sysQuartzDao.selectOne(wrapper);
        if (Objects.nonNull(one)) {
            throw new CustomException(ErrorCodeEnum.DATA_IS_EXIST);
        }
        if (!CronExpression.isValidExpression(sysQuartz.getCronExpression())) {
            String cron = CronUtil.toCronExpression(sysQuartz.getCronExpression());
            sysQuartz.setCronExpression(cron);
        }
        sysQuartz.setStatus(QuartzStatusEnum.WAIT_START.getCode());
        sysQuartzDao.add(sysQuartz);
        scheduleAdd(sysQuartz);
        return sysQuartz;
    }

    /**
     * 暂停任务
     *
     * @param sysQuartz
     * @return
     */
    @Override
    @Transactional
    public Integer pauseJob(SysQuartz sysQuartz) throws SchedulerException {
        sysQuartz.setStatus(QuartzStatusEnum.SUSPEND.getCode());
        int update = sysQuartzDao.update(sysQuartz);
        if (update > 0) {
            JobKey jobKey = getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName());
            scheduler.pauseJob(jobKey);
            log.info("任务暂停：【{}】", jobKey);
        }
        return update;
    }

    /**
     * 恢复启动任务
     *
     * @param sysQuartz
     * @return
     */
    @Override
    @Transactional
    public Integer resumeJob(SysQuartz sysQuartz) throws SchedulerException {
        sysQuartz.setStatus(QuartzStatusEnum.WAIT_START.getCode());
        int update = sysQuartzDao.update(sysQuartz);
        if (update > 0) {
            JobKey jobKey = getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName());
            scheduler.resumeJob(jobKey);
            log.info("任务恢复：【{}】", jobKey);
        }
        return update;
    }

    @Override
    @Transactional
    public void updateCron(SysQuartz sysQuartz) throws SchedulerException, ParseException {
        if (!CronExpression.isValidExpression(sysQuartz.getCronExpression())) {
            String cron = CronUtil.toCronExpression(sysQuartz.getCronExpression());
            sysQuartz.setCronExpression(cron);
        }
        int update = sysQuartzDao.update(sysQuartz);
        if (update > 0) {
            JobKey jobKey = JobKey.jobKey(sysQuartz.getClassName(), sysQuartz.getGroupName());
            // 如果存在先移除，防止数据问题
            if (scheduler.checkExists(jobKey)) {
                scheduler.deleteJob(jobKey);
            }
            scheduleAdd(sysQuartz);
            log.info("更新成功：【{}】", jobKey);
        }
    }

    /**
     * 通过主键删除任务
     *
     * @param id 主键
     * @return 是否成功
     */
    @Transactional
    @Override
    public boolean delete(Integer id) throws SchedulerException {
        SysQuartz sysQuartz = sysQuartzDao.queryById(id);
        int i = sysQuartzDao.delete(id);
        if (i > 0) {
            scheduler.deleteJob(getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName()));
            log.info("任务删除：【{}】", getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName()));
        }
        return sysQuartzDao.delete(id) > 0;
    }

    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    @Override
    public SysQuartz queryById(Integer id) {
        return sysQuartzDao.queryById(id);
    }

    /**
     * 分页查询
     *
     * @param sysQuartz 筛选条件
     * @param pageParam 分页参数
     * @return 查询结果
     */
    @Override
    public PageInfo<SysQuartz> page(SysQuartz sysQuartz, PageParam pageParam) {
        PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize());
        return new PageInfo<>(sysQuartzDao.selectAll(sysQuartz));
    }

    /**
     * 修改数据
     *
     * @param sysQuartz 实例对象
     * @return 实例对象
     */
    @Override
    public SysQuartz update(SysQuartz sysQuartz) {
        sysQuartzDao.update(sysQuartz);
        return queryById(sysQuartz.getId());
    }

    /**
     * 构建定时任务
     *
     * @param sysQuartz
     * @throws SchedulerException
     */
    public void scheduleAdd(SysQuartz sysQuartz) throws SchedulerException {
        if (!scheduler.isStarted()) {
            scheduler.start();
        }
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
                .withIdentity(getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName()))
                .usingJobData(sysQuartz.getClassName(), sysQuartz.getParam())
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(sysQuartz.getClassName(), sysQuartz.getGroupName()))
                .withSchedule(CronScheduleBuilder.cronSchedule(sysQuartz.getCronExpression()))
                .build();
        // 放入参数，运行时的方法可以获取
        jobDetail.getJobDataMap().put(QuartzConstants.QUARTZ_KEY, sysQuartz);
        // 任务已经存在则删除任务
        if (scheduler.checkExists(getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName()))) {
            scheduler.deleteJob(getJobKey(sysQuartz.getClassName(), sysQuartz.getGroupName()));
        }
        // 时间不过期
        if (Objects.nonNull(getNextExecution(sysQuartz.getCronExpression()))) {
            scheduler.scheduleJob(jobDetail, trigger);
        }
    }

    /**
     * 获取 JobKey
     *
     * @param name
     * @param group
     * @return
     */
    private JobKey getJobKey(String name, String group){
        return JobKey.jobKey(name, group);
    }

    /**
     * 返回下一个执行时间根据给定的Cron表达式，过期返回null
     *
     * @param cronExpression Cron表达式
     * @return Date 下次Cron表达式执行时间
     */
    public Date getNextExecution(String cronExpression)
    {
        try
        {
            CronExpression cron = new CronExpression(cronExpression);
            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
        }
        catch (ParseException e)
        {
            throw new IllegalArgumentException(e.getMessage());
        }
    }
}
